﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using DomainDriver.CommonUtilities.Reflectors;
using DomainDriver.DomainModeling.Attributes;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.DomainObjects;
using DomainDriver.DomainModeling.Queries;

namespace DomainDriver.DomainModeling.StorageManagers
{
    public class TransientStorageManager<KEY, KEYED_DOMAIN_OBJECT> : QueryableStorageManagerBase<KEY, KEYED_DOMAIN_OBJECT>
        where KEYED_DOMAIN_OBJECT : IKeyedDomainObject<KEY, KEYED_DOMAIN_OBJECT>
    {
        protected static bool s_DefaultUseLocking = false;
        public static bool DefaultUseLocking
        {
            get { return s_DefaultUseLocking; }
            set { s_DefaultUseLocking = value; }
        }

        protected const string KeyPropertyName = "Key";

        protected object m_LockObject;
        protected bool m_UseLocking;
        protected Dictionary<KEY, KEYED_DOMAIN_OBJECT> m_ObjectStore;
        protected PropertyInfo m_KeyProperty;
        protected IDictionary<Type, IList<PropertyInfo>> m_DomainObjectPropertiesByAttributeType;

        public TransientStorageManager(IDataProvider dataProvider)
            : base(dataProvider)
        {
            m_LockObject = new object();
            m_UseLocking = DefaultUseLocking;
            m_ObjectStore = new Dictionary<KEY, KEYED_DOMAIN_OBJECT>();
            m_KeyProperty = ClassReflector.GetPropertyByName(typeof(KEYED_DOMAIN_OBJECT), KeyPropertyName);
            m_DomainObjectPropertiesByAttributeType = ClassReflector.GetPropertiesForAttributeType(typeof(KEYED_DOMAIN_OBJECT));
        }

        public bool UseLocking
        {
            get { return m_UseLocking; }
            set { m_UseLocking = value; }
        }

        protected override IEnumerable<KEYED_DOMAIN_OBJECT> GetQueryableDataStore()
        {
            if (UseLocking)
            {
                lock (m_LockObject)
                {
                    return new List<KEYED_DOMAIN_OBJECT>(m_ObjectStore.Values);
                }
            }
            else
            {
                return new List<KEYED_DOMAIN_OBJECT>(m_ObjectStore.Values);
            }
        }

        protected override Func<KEYED_DOMAIN_OBJECT, bool> GetKeyMatchingPredicate(ICollection<KEY> keys)
        {
            if (UseLocking)
            {
                lock (m_LockObject)
                {
                    Func<KEYED_DOMAIN_OBJECT, bool> keyMatchingPredicate = ((KEYED_DOMAIN_OBJECT domainObject) => keys.Contains(domainObject.Key));
                    return keyMatchingPredicate;
                }
            }
            else
            {
                Func<KEYED_DOMAIN_OBJECT, bool> keyMatchingPredicate = ((KEYED_DOMAIN_OBJECT domainObject) => keys.Contains(domainObject.Key));
                return keyMatchingPredicate;
            }
        }

        protected override ICollection<KEY> ReadKeys(Func<KEYED_DOMAIN_OBJECT, bool> predicate)
        {
            if (UseLocking)
            {
                lock (m_LockObject)
                {
                    IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects = m_ObjectStore.Values.Where<KEYED_DOMAIN_OBJECT>(predicate);
                    List<KEY> matchingKeys = new List<KEY>();

                    foreach (KEYED_DOMAIN_OBJECT domainObject in domainObjects)
                    {
                        matchingKeys.Add(domainObject.Key);
                    }
                    return matchingKeys;
                }
            }
            else
            {
                IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects = m_ObjectStore.Values.Where<KEYED_DOMAIN_OBJECT>(predicate);
                List<KEY> matchingKeys = new List<KEY>();

                foreach (KEYED_DOMAIN_OBJECT domainObject in domainObjects)
                {
                    matchingKeys.Add(domainObject.Key);
                }
                return matchingKeys;
            }
        }

        protected override ICollection<DESC> ReadDescriptors<DESC>(Func<KEYED_DOMAIN_OBJECT, bool> predicate)
        {
            if (UseLocking)
            {
                lock (m_LockObject)
                {
                    IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects = m_ObjectStore.Values.Where<KEYED_DOMAIN_OBJECT>(predicate);
                    List<DESC> matchingDescriptors = new List<DESC>();

                    foreach (KEYED_DOMAIN_OBJECT domainObject in domainObjects)
                    {
                        DESC descriptor = new DESC();
                        descriptor.DescribedObject = domainObject;
                        matchingDescriptors.Add(descriptor);
                    }
                    return matchingDescriptors;
                }
            }
            else
            {
                IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects = m_ObjectStore.Values.Where<KEYED_DOMAIN_OBJECT>(predicate);
                List<DESC> matchingDescriptors = new List<DESC>();

                foreach (KEYED_DOMAIN_OBJECT domainObject in domainObjects)
                {
                    DESC descriptor = new DESC();
                    descriptor.DescribedObject = domainObject;
                    matchingDescriptors.Add(descriptor);
                }
                return matchingDescriptors;
            }
        }

        protected override ICollection<KEYED_DOMAIN_OBJECT> ReadStoredObjects(Func<KEYED_DOMAIN_OBJECT, bool> predicate)
        {
            if (UseLocking)
            {
                lock (m_LockObject)
                {
                    IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects = m_ObjectStore.Values.Where<KEYED_DOMAIN_OBJECT>(predicate);
                    List<KEYED_DOMAIN_OBJECT> matchingObjects = new List<KEYED_DOMAIN_OBJECT>();

                    foreach (KEYED_DOMAIN_OBJECT domainObject in domainObjects)
                    {
                        matchingObjects.Add(domainObject);
                    }
                    return matchingObjects;
                }
            }
            else
            {
                IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects = m_ObjectStore.Values.Where<KEYED_DOMAIN_OBJECT>(predicate);
                List<KEYED_DOMAIN_OBJECT> matchingObjects = new List<KEYED_DOMAIN_OBJECT>();

                foreach (KEYED_DOMAIN_OBJECT domainObject in domainObjects)
                {
                    matchingObjects.Add(domainObject);
                }
                return matchingObjects;
            }
        }

        protected override void WriteObject(KEYED_DOMAIN_OBJECT domainObject, ChangeActionType changeAction)
        {
            if (UseLocking)
            {
                lock (m_LockObject)
                {
                    if (changeAction == ChangeActionType.Delete)
                    {
                        m_ObjectStore.Remove(domainObject.Key);
                        return;
                    }

                    Dictionary<KEY, KEYED_DOMAIN_OBJECT> otherDomainObjectsByKey = new Dictionary<KEY, KEYED_DOMAIN_OBJECT>(m_ObjectStore);
                    List<KEYED_DOMAIN_OBJECT> otherDomainObjects = new List<KEYED_DOMAIN_OBJECT>(otherDomainObjectsByKey.Values);
                    if (changeAction == ChangeActionType.Update)
                    {
                        otherDomainObjectsByKey.Remove(domainObject.Key);
                        otherDomainObjects = new List<KEYED_DOMAIN_OBJECT>(otherDomainObjectsByKey.Values);
                    }

                    Type uniqueAttributeType = typeof(UniqueAttribute);
                    if (m_DomainObjectPropertiesByAttributeType.ContainsKey(uniqueAttributeType))
                    {
                        IList<PropertyInfo> uniqueProperties = m_DomainObjectPropertiesByAttributeType[uniqueAttributeType];
                        AssertUniqueAttribute(domainObject, otherDomainObjects, uniqueProperties);
                    }

                    Type notNullAttributeType = typeof(NotNullAttribute);
                    if (m_DomainObjectPropertiesByAttributeType.ContainsKey(notNullAttributeType))
                    {
                        IList<PropertyInfo> notNullProperties = m_DomainObjectPropertiesByAttributeType[notNullAttributeType];
                        AssertNotNullAttribute(domainObject, notNullProperties);
                    }

                    Type notEqualToValueAttributeType = typeof(NotEqualToValueAttribute);
                    if (m_DomainObjectPropertiesByAttributeType.ContainsKey(notEqualToValueAttributeType))
                    {
                        IList<PropertyInfo> notEqualToValueProperties = m_DomainObjectPropertiesByAttributeType[notEqualToValueAttributeType];
                        AssertNotEqualToValueAttribute(domainObject, notEqualToValueProperties);
                    }

                    KEYED_DOMAIN_OBJECT copy = domainObject.Copy();

                    if (changeAction == ChangeActionType.Create)
                    {
                        m_ObjectStore.Add(copy.Key, copy);
                        return;
                    }
                    if (changeAction == ChangeActionType.Update)
                    {
                        m_ObjectStore[copy.Key] = copy;
                        return;
                    }
                }
            }
            else
            {
                if (changeAction == ChangeActionType.Delete)
                {
                    m_ObjectStore.Remove(domainObject.Key);
                    return;
                }

                Dictionary<KEY, KEYED_DOMAIN_OBJECT> otherDomainObjectsByKey = new Dictionary<KEY, KEYED_DOMAIN_OBJECT>(m_ObjectStore);
                List<KEYED_DOMAIN_OBJECT> otherDomainObjects = new List<KEYED_DOMAIN_OBJECT>(otherDomainObjectsByKey.Values);
                if (changeAction == ChangeActionType.Update)
                {
                    otherDomainObjectsByKey.Remove(domainObject.Key);
                    otherDomainObjects = new List<KEYED_DOMAIN_OBJECT>(otherDomainObjectsByKey.Values);
                }

                Type uniqueAttributeType = typeof(UniqueAttribute);
                if (m_DomainObjectPropertiesByAttributeType.ContainsKey(uniqueAttributeType))
                {
                    IList<PropertyInfo> uniqueProperties = m_DomainObjectPropertiesByAttributeType[uniqueAttributeType];
                    AssertUniqueAttribute(domainObject, otherDomainObjects, uniqueProperties);
                }

                Type notNullAttributeType = typeof(NotNullAttribute);
                if (m_DomainObjectPropertiesByAttributeType.ContainsKey(notNullAttributeType))
                {
                    IList<PropertyInfo> notNullProperties = m_DomainObjectPropertiesByAttributeType[notNullAttributeType];
                    AssertNotNullAttribute(domainObject, notNullProperties);
                }

                Type notEqualToValueAttributeType = typeof(NotEqualToValueAttribute);
                if (m_DomainObjectPropertiesByAttributeType.ContainsKey(notEqualToValueAttributeType))
                {
                    IList<PropertyInfo> notEqualToValueProperties = m_DomainObjectPropertiesByAttributeType[notEqualToValueAttributeType];
                    AssertNotEqualToValueAttribute(domainObject, notEqualToValueProperties);
                }

                KEYED_DOMAIN_OBJECT copy = domainObject.Copy();

                if (changeAction == ChangeActionType.Create)
                {
                    m_ObjectStore.Add(copy.Key, copy);
                    return;
                }
                if (changeAction == ChangeActionType.Update)
                {
                    m_ObjectStore[copy.Key] = copy;
                    return;
                }
            }
        }

        #region Assertion Support

        private void AssertUniqueAttribute(KEYED_DOMAIN_OBJECT domainObject, List<KEYED_DOMAIN_OBJECT> otherDomainObjects, IList<PropertyInfo> uniqueProperties)
        {
            foreach (PropertyInfo propertyInfo in uniqueProperties)
            {
                object currentValue = propertyInfo.GetValue(domainObject, null);

                foreach (KEYED_DOMAIN_OBJECT otherDomainObject in otherDomainObjects)
                {
                    object otherValue = propertyInfo.GetValue(otherDomainObject, null);
                    if (currentValue.Equals(otherValue))
                    {
                        throw new ApplicationException();
                    }
                }
            }
        }

        private void AssertNotNullAttribute(KEYED_DOMAIN_OBJECT domainObject, IList<PropertyInfo> notNullProperties)
        {
            foreach (PropertyInfo propertyInfo in notNullProperties)
            {
                object currentValue = propertyInfo.GetValue(domainObject, null);

                if (currentValue == null)
                {
                    throw new ApplicationException();
                }
            }
        }

        private void AssertNotEqualToValueAttribute(KEYED_DOMAIN_OBJECT domainObject, IList<PropertyInfo> notEqualToValueProperties)
        {
            foreach (PropertyInfo propertyInfo in notEqualToValueProperties)
            {
                Attribute untypedAttribute = propertyInfo.GetCustomAttributes(typeof(NotEqualToValueAttribute), true)[0] as Attribute;
                NotEqualToValueAttribute typedAttribute = untypedAttribute as NotEqualToValueAttribute;
                object currentValue = propertyInfo.GetValue(domainObject, null);

                if (currentValue.Equals(typedAttribute.InvalidValue))
                {
                    throw new ApplicationException();
                }
            }
        }

        #endregion
    }
}